#pragma once

#include "AutoRefPtr.h"
#include "Coroutine/Future.h"

namespace AsyncLib
{

template<typename T>
struct IsFuture : std::false_type {};

template<typename T>
struct IsFuture<CFuture<T>> : std::true_type {};

template<typename T>
class CStateManager : public CRefCounted
{
public:
	using type = T;

	static_assert(!IsFuture<T>::value);

	~CStateManager() override
	{
		// All captured variables have been destructed here
		m_pState->Complete();
	}

	// Do nothing because using placement new
	void operator delete(void *)
	{
	}

	void SetState(std::shared_ptr<CAssocState<T>> pState)
	{
		assert(!m_pState && pState);
		m_pState = std::move(pState);
	}

	const auto& GetState() const
	{
		return m_pState;
	}

private:
	std::shared_ptr<CAssocState<T>> m_pState;
};

template<typename Fn, typename... Args>
class CPkgedTask : public CStateManager<std::invoke_result_t<Fn, Args...>>
{
public:
	template<typename Fn2, typename... Args2>
	CPkgedTask(Fn2&& fn, Args2&&... args) : m_fn(std::forward<Fn2>(fn)), m_args(std::forward<Args2>(args)...)
	{
	}

	void Execute()
	{
		try
		{
			// Use std::move because it is called only once
			if constexpr (std::is_void_v<typename CPkgedTask::type>)
			{
				std::apply(std::move(m_fn), std::move(m_args));
				this->GetState()->SetValue();
			}
			else
				this->GetState()->SetValue(std::apply(std::move(m_fn), std::move(m_args)));
		}
		catch (...)
		{
			this->GetState()->SetException(std::current_exception());
		}
	}

private:
	Fn m_fn;
	std::tuple<Args...> m_args;
};

template<>
class CPkgedTask<nullptr_t> : public CStateManager<nullptr_t>
{
public:
	CPkgedTask(nullptr_t)
	{
	}

	void Execute()
	{
		this->GetState()->SetValue(nullptr);
	}
};

} // namespace AsyncLib
